using System;

using Atomic.Thermodynamics.StateEquations;

namespace Atomic.Thermodynamics
{
	[Serializable]
	public abstract class GibbsPotential : IGibbsPotential
	{
		public abstract double FreeEnergy(double temperature, double pressure);

		public abstract double Volume(double temperature, double pressure);

		public abstract double VolumetricThermalExpansionCoefficient(double temperature, double pressure);

		public static GibbsPotential FixedTemperature(double fixedTemperature, double freeEnergy)
		{
			return new FixedTemperaturePotential(fixedTemperature, freeEnergy);
		}

		/// <summary>
		/// Legendre transform of the Helmholtz potential as given by an generalized equation of state. Currently only supports zero pressure.
		/// </summary>
		public static GibbsPotential StateEquationGibbsPotential(IStateEquationPotential potential)
		{
			return new StateEquationPotential(potential);
		}

		/*public double FreeEnergy(double temperature, double pressure)
		{
			throw new System.NotImplementedException();
		}

		public double Volume(double temperature, double pressure)
		{
			if (temperature < 0.0)
			{
				throw new ArgumentOutOfRangeException();
			}

			throw new System.NotImplementedException();
		}

		public double ThermalExpansionCoefficient(double temperature, double pressure)
		{
			double h = 0.0001;

			double v = Volume(temperature, pressure);
			double vh = Volume(temperature + h, pressure);
			double alpha = 1.0 / v * (vh - v) / h;

			// Linear thermal expansion.
			alpha /= 3.0;

			return alpha;
		}*/

		IHelmholtzPotential IGibbsPotential.HelmholtzPotential
		{
			get
			{
				throw new NotImplementedException();
			}
		}

		[Serializable]
		private class FixedTemperaturePotential : GibbsPotential
		{
			private double fixedTemperature, freeEnergy;

			public FixedTemperaturePotential(double fixedTemperature, double freeEnergy)
			{
				if (fixedTemperature < 0.0)
				{
					throw new ArgumentOutOfRangeException();
				}

				this.fixedTemperature = fixedTemperature;
				this.freeEnergy = freeEnergy;
			}

			public override double FreeEnergy(double temperature, double pressure)
			{
				if (temperature != fixedTemperature || pressure != 0.0)
				{
					throw new ArgumentOutOfRangeException();
				}

				return freeEnergy;
			}

			public override double Volume(double temperature, double pressure)
			{
				//throw new NotSupportedException();
				return double.NaN;
			}

			public override double VolumetricThermalExpansionCoefficient(double temperature, double pressure)
			{
				// Can't compute for this potential.
				throw new NotSupportedException();
			}
		}

		[Serializable]
		private class StateEquationPotential : GibbsPotential
		{
			private IStateEquationPotential potential;

			public StateEquationPotential(IStateEquationPotential potential)
			{
				if (potential == null)
				{
					throw new ArgumentNullException();
				}

				this.potential = potential;
			}

			public override double FreeEnergy(double temperature, double pressure)
			{
				double volume = Volume(temperature, pressure);

				// http://en.wikipedia.org/w/index.php?title=Thermodynamic_potential&oldid=576974308#Description_and_interpretation
				return potential.FreeEnergy(temperature, volume) + pressure * volume;
			}

			public override double Volume(double temperature, double pressure)
			{
				if (pressure != 0.0)
				{
					throw new NotImplementedException();
				}

				return potential.StateEquation(temperature).EquilibriumVolume;
			}

			public override double VolumetricThermalExpansionCoefficient(double temperature, double pressure)
			{
				// How to compute this mixed derivative?
				Console.WriteLine("FIXME numerical derivative");

				double h = 0.0001;

				double v = Volume(temperature, pressure);
				double vh = Volume(temperature + h, pressure);
				double alpha = 1.0 / v * (vh - v) / h;

				return alpha;
			}
		}
	}
}
